/*
 * Copyright (c) 2008, Schley Andrew Kutz <sakutz@gmail.com> All rights
 * reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer. Redistributions in binary
 * form must reproduce the above copyright notice, this list of conditions and
 * the following disclaimer in the documentation and/or other materials provided
 * with the distribution. Neither the name of l o s t c r e a t i o n s nor the
 * names of its contributors may be used to endorse or promote products derived
 * from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package com.lostcreations.vitoolkitforjava;

import java.rmi.RemoteException;
import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;
import com.vmware.vim25.*;
import org.apache.commons.cli.*;
import java.io.*;

/**
 * This class is used to create a session to and interact with a VI server.
 * 
 * @author Schley Andrew Kutz &lt;sakutz@gmail.com&gt;
 */
public class Session
{

	// <region> Enums

	/**
	 * The format in which to save the session cookie.
	 * 
	 * @author Schley Andrew Kutz &lt;sakutz@gmail.com&gt;
	 */
	public enum SaveSessionCookieFormat
	{
		/**
		 * The resulting cookie file is the same as the cookie format Axis uses.
		 */
		AXIS,
		
		/**
		 * The resulting cookie file is the same as the one VI Perl creates.
		 */
		PERL,

		/**
		 * The resulting cookie file contains only the session token value.
		 */
		COOKIE_VALUE_ONLY,
	};

	/**
	 * The different types of VI entities.
	 * 
	 * @author Schley Andrew Kutz &lt;sakutz@gmail.com&gt;
	 */
	public enum EntityTypes
	{
		/**
		 * ComputeResource
		 */
		ComputeResource,

		/**
		 * ClusterComputeResource
		 */
		ClusterComputeResource,

		/**
		 * Datacenter
		 */
		Datacenter,

		/**
		 * Folder
		 */
		Folder,

		/**
		 * HostSystem
		 */
		HostSystem,

		/**
		 * ResourcePool
		 */
		ResourcePool,

		/**
		 * VirtualMachine
		 */
		VirtualMachine,
	};

	// </editor-fold>

	// </region>

	// <region> Semi-Constants

	// <editor-fold defaultstate="collapsed" desc="Semi-Constants">

	// If there are environment variables for options then
	// set the default values of those options to the value
	// of the environment variables.
	private String VI_PASSTHROUGHPACKAGE =
		System.getenv( "VI_PASSHTHROUGHPACKAGE" );
	private String VI_PASSWORD = System.getenv( "VI_PASSWORD" );
	private String VI_PORTNUMBER = System.getenv( "VI_PORTNUMBER" );
	private String VI_PROTOCOL = System.getenv( "VI_PROTOCOL" );
	private String VI_SAVESESSIONFILE = System.getenv( "VI_SAVESESSIONFILE" );
	private String VI_SERVER = System.getenv( "VI_SERVER" );
	private String VI_SERVICEPATH = System.getenv( "VI_SERVICEPATH" );
	private String VI_SESSIONFILE = System.getenv( "VI_SESSIONFILE" );
	private String VI_URL = System.getenv( "VI_URL" );
	private String VI_USERNAME = System.getenv( "VI_USERNAME" );

	// </editor-fold>

	// </region>

	// <region> Fields

	// <editor-fold defaultstate="collapsed" desc="Fields">

	/**
	 * The string to print out before the generated usage text.
	 */
	private String m_help_prefix = "Placeholder";

	/**
	 * The options for the program.
	 */
	private Options m_options = null;

	/**
	 * Command-line arguments.
	 */
	private String[] m_args = null;

	/**
	 * The parsed command line.
	 */
	private CommandLine m_cmdline = null;

	/**
	 * True if there is an open connection to the VI server; otherwise false.
	 */
	private boolean m_is_connected = false;

	/**
	 * This is the class for the very special managed object ServiceInstance,
	 * the root of all things VI SDK.
	 */
	private VimPortType m_vim_svc = null;

	/**
	 * This is the class that contains information relative to the current
	 * ServiceInstance.
	 */
	private ServiceContent m_vim_svc_content = null;

	// </editor-fold>

	// </region>

	// <region> Properties

	// <editor-fold defaultstate="collapsed" desc="Properties">

	/**
	 * Gets the string to print out before the generated usage text.
	 * 
	 * @return The string to print out before the generated usage text.
	 */
	public String getHelpPrefix()
	{
		return (m_help_prefix);
	}

	/**
	 * Sets the string to print out before the generated usage text.
	 * 
	 * @param value The string to print out before the generated usage text.
	 */
	public void setHelpPrefix( String value )
	{
		m_help_prefix = value;
	}

	/**
	 * True if there is an open connection to the VI server; otherwise false.
	 * 
	 * @return The connection status.
	 */
	public boolean getIsConnected()
	{
		return (m_is_connected);
	}

	/**
	 * This is the class that contains information relative to the current
	 * ServiceInstance.
	 * 
	 * @return The class that contains information relative to the current
	 *         ServiceInstance.
	 */
	public VimPortType getService()
	{
		return (m_vim_svc);
	}

	/**
	 * This is the class for the very special managed object ServiceInstance,
	 * the root of all things VI SDK.
	 * 
	 * @return The class for the very special managed object ServiceInstance,
	 *         the root of all things VI SDK.
	 */
	public ServiceContent getServiceContent()
	{
		return (m_vim_svc_content);
	}

	/**
	 * Returns the URL used to connect to the VI server.
	 * 
	 * @return The URL used to connect to the VI server.
	 */
	public String getVIUrl()
	{
		String url =
			hasOption( "url" ) ? getOptionValue( "url" ) : String.format(
				"%1$s://%2$s:%3$s%4$s", getOptionValue( "protocol" ),
				getOptionValue( "server" ), hasOption( "port" )
					? getOptionValue( "port" ) : getOptionValue( "protocol" )
						.equals( "http" ) ? "80" : "443", getOptionValue(
					"servicepath" ).startsWith( "/" )
					? getOptionValue( "servicepath" ) : "/"
						+ getOptionValue( "servicepath" ) );
		return (url);
	}

	// </editor-fold>

	// </region>

	// <region> Constructors

	// <editor-fold defaultstate="collapsed" desc="Constructors">

	/**
	 * create a new Session object.
	 * 
	 * @param args Command line arguments to pass to this session object.
	 */
	public Session( String[] args )
	{
		m_args = args;

		// Initialize the default options
		initDefaultOptions();
	}

	// </editor-fold>

	// </region>

	// <region> Methods

	// <editor-fold defaultstate="collapsed" desc="Methods">

	/**
	 * Initialize the default options.
	 */
	private void initDefaultOptions()
	{
		// create an options object.
		m_options = new Options();

		// Add a new option.
		m_options.addOption( OptionBuilder.withLongOpt( "help" )
			.withDescription( "Display usage information for the program " )
			.create() );

		m_options.addOption( OptionBuilder.withLongOpt( "ignoresslerrors" )
			.withDescription(
				"Ignore certificate mismatch errors "
					+ "(variable VI_IGNORESSLERRORS)" ).create() );

		/*
		 * TODO: Get passthrough authentication working.
		 * 
		 * m_options.addOption( OptionBuilder .withLongOpt( "passthroughauth" )
		 * .withDescription( "Attempt to use pass-through authentication" )
		 * .create() );
		 * 
		 * m_options.addOption( OptionBuilder .withLongOpt(
		 * "passthroughauthpackage" ) .hasArg() .withDescription( "Pass-through
		 * authentication negotiation package" ) .create() );
		 */

		m_options.addOption( OptionBuilder
			.withLongOpt( "password" )
			.hasArg()
			.withDescription( "Password (variable VI_PASSWORD)" )
			.create() );

		m_options.addOption( OptionBuilder
			.withLongOpt( "portnumber" )
			.hasArg()
			.withDescription(
				"Port used to connect to server (variable VI_PORTNUMBER)" )
			.create() );

		m_options.addOption( OptionBuilder
			.withLongOpt( "protocol" )
			.hasArg()
			.withDescription(
				"Protocol used to connect to server "
				+ "(variable VI_PROTOCOL, default 'https')" )
			.create() );

		m_options.addOption( OptionBuilder
			.withLongOpt( "server" )
			.hasArg()
			.withDescription(
				"VI server to connect to. Required if url is not present "
				+ "(variable, VI_SERVER, default 'localhost')" )
			.create() );

		m_options.addOption( OptionBuilder
			.withLongOpt( "savesessionfile" )
			.hasArg()
			.withDescription(
				"File to save session ID/cookie to utilize "
				+ "(variable, VI_SAVESESSIONFILE)" )
			.withType( java.io.File.class )
			.create() );

		m_options.addOption( OptionBuilder
			.withLongOpt( "servicepath" )
			.hasArg()
			.withDescription(
				"Service path used to connect to server "
				+ "(variable, VI_SERVICEPATH, default '/sdk/webService')" )
			.create() );

		m_options.addOption( OptionBuilder
			.withLongOpt( "sessionfile" )
			.hasArg()
			.withDescription(
				"File containg session ID/cookie to utilize "
					+ "(variable, VI_SESSIONFILE)" )
			.withType( java.io.File.class )
			.create() );

		m_options.addOption( OptionBuilder
			.withLongOpt( "url" )
			.hasArg()
			.withDescription(
				"VI SDK URL to connect to. Required if server is not "
				+ "present (variable, VI_URL)" )
			.withType( java.net.URI.class )
			.create() );

		m_options.addOption( OptionBuilder
			.withLongOpt( "username" )
			.hasArg()
			.withDescription( "Username (variable, VI_USERNAME)" )
			.create() );

		m_options.addOption( OptionBuilder
			.withLongOpt( "verbose" )
			.withDescription(
				"Display additional debugging information "
				+ "(variable, VI_VERBOSE)" )
			.create() );

		m_options.addOption( OptionBuilder
			.withLongOpt( "version" )
			.withDescription( "Display version information for the script" )
			.create() );
	}

	/**
	 * Validate the options and the given command line arguments.
	 * 
	 * @throws ParseException If there is an error parsing the given arguments.
	 */
	public void validateOptions() throws ParseException
	{
		CommandLineParser parser = new PosixParser();

		m_cmdline = parser.parse( m_options, m_args );

		// If anywhere there is a help flag then show help
		// and exit the app.
		if ( m_cmdline.hasOption( "help" ) )
		{
			printUsage();
			System.exit( 0 );
		}

		// Get the username and password by prompting the user
		// if they were not specified in an argument or via
		// environment variables.
		if ( !hasOption( "sessionfile" )
			&& getOptionValue( "username" ) == null )
		{
			System.out.print( "Username:" );
			VI_USERNAME = System.console().readLine();
		}
		if ( !hasOption( "sessionfile" )
			&& getOptionValue( "password" ) == null )
		{
			System.out.print( "Password: " );
			// TODO: Replace System.console
			//VI_PASSWORD = String.valueOf( System.console().readPassword() );
			System.out.println();
		}
	}

	/**
	 * Print the help/usage statement.
	 * 
	 * @param prefix The text to print out before the generated usage prompt.
	 */
	public void printUsage( String prefix )
	{
		HelpFormatter hf = new HelpFormatter();
		hf.printHelp( prefix, m_options );
	}

	/**
	 * Print the help/usage statement.
	 */
	public void printUsage()
	{
		printUsage( m_help_prefix );
	}

	/**
	 * Add user-defined options.
	 * 
	 * @param options The options to add.
	 */
	@SuppressWarnings( "unchecked" )
	public void addOptions( Options options )
	{
		for ( Iterator<Option> i = options.getOptions().iterator(); 
			i.hasNext(); )
		{
			m_options.addOption( i.next() );

		}
	}

	/**
	 * Returns an options value. If the option was not set, but there is an
	 * environment variable set for it, it will return the value of the
	 * environment variable.
	 * 
	 * @param optionName The name of the option which to get the value for.
	 * @return The value of the option. A null value is returned if the option
	 *         is not found.
	 */
	public String getOptionValue( String optionName )
	{
		String value = null;
		if ( optionName.equals( "passthroughauthpackage" ) )
		{
			if ( m_cmdline.hasOption( "passthroughpackage" ) )
				value = m_cmdline.getOptionValue( "passthroughpackage" );
			else if ( VI_PASSTHROUGHPACKAGE == null )
				value = "Negotiate";
			else value = VI_PASSTHROUGHPACKAGE;
		}
		else if ( optionName.equals( "password" ) )
		{
			if ( m_cmdline.hasOption( "password" ) )
				value = m_cmdline.getOptionValue( "password" );
			else value = VI_PASSWORD;
		}
		else if ( optionName.equals( "portnumber" ) )
		{
			if ( m_cmdline.hasOption( "portnumber" ) )
				value = m_cmdline.getOptionValue( "portnumber" );
			else value = VI_PORTNUMBER;
		}
		else if ( optionName.equals( "protocol" ) )
		{
			if ( m_cmdline.hasOption( "protocol" ) )
				value = m_cmdline.getOptionValue( "protocol" );
			else if ( VI_PROTOCOL == null )
				value = "https";
			else value = VI_PROTOCOL;
		}
		else if ( optionName.equals( "savesessionfile" ) )
		{
			if ( m_cmdline.hasOption( "savesessionfile" ) )
				value = m_cmdline.getOptionValue( "savesessionfile" );
			else value = VI_SAVESESSIONFILE;
		}
		else if ( optionName.equals( "server" ) )
		{
			if ( m_cmdline.hasOption( "server" ) )
				value = m_cmdline.getOptionValue( "server" );
			else if ( VI_SERVER == null )
				value = "localhost";
			else value = VI_SERVER;
		}
		else if ( optionName.equals( "servicepath" ) )
		{
			if ( m_cmdline.hasOption( "servicepath" ) )
				value = m_cmdline.getOptionValue( "servicepath" );
			else if ( VI_SERVICEPATH == null )
				value = "/sdk/webService";
			else value = VI_SERVICEPATH;
		}
		else if ( optionName.equals( "sessionfile" ) )
		{
			if ( m_cmdline.hasOption( "sessionfile" ) )
				value = m_cmdline.getOptionValue( "sessionfile" );
			else value = VI_SESSIONFILE;
		}
		else if ( optionName.equals( "url" ) )
		{
			if ( m_cmdline.hasOption( "url" ) )
				value = m_cmdline.getOptionValue( "url" );
			else value = VI_URL;
		}
		else if ( optionName.equals( "username" ) )
		{
			if ( m_cmdline.hasOption( "username" ) )
				value = m_cmdline.getOptionValue( "username" );
			else value = VI_USERNAME;
		}

		return (value);
	}

	/**
	 * Determines if an option was present on the command line.
	 * 
	 * @param optionName The name of the option.
	 * @return True if the option was present; otherwise false.
	 */
	public boolean hasOption( String optionName )
	{
		assert optionName != null;

		boolean value = false;

		if ( optionName.equals( "ignoresslerrors" ) )
		{
			if ( m_cmdline.hasOption( "ignoresslerrors" ) )
				value = true;
			else if ( System.getenv( "VI_IGNORESSLERRORS" ) != null )
				value = true;
		}
		else if ( optionName.equals( "passthroughauth" ) )
		{
			if ( m_cmdline.hasOption( "passthroughauth" ) )
				value = true;
			else if ( System.getenv( "VI_PASSTHROUGH" ) != null ) value = true;
		}
		else if ( optionName.equals( "verbose" ) )
		{
			if ( m_cmdline.hasOption( "verbose" ) )
				value = true;
			else if ( System.getenv( "VI_VERBOSE" ) != null ) value = true;
		}
		else
		{
			value = m_cmdline.hasOption( optionName );
		}

		return (value);
	}

	/**
	 * Connects to a VI server.
	 * 
	 * @throws java.net.MalformedURLException If the {@code getVIUrl} is 
	 * invalid.
	 * @throws javax.xml.rpc.ServiceException If there is a problem 
	 * communicating with the VI server.
	 * @throws java.rmi.RemoteException If there is a problem communicating
	 * with the VI server.
	 * @throws FileNotFoundException If the session file is not found.
	 * @throws IOException If there is a problem reading the session
	 * file.
	 */
	public void connect() throws 
		java.net.MalformedURLException,
		javax.xml.rpc.ServiceException, 
		java.rmi.RemoteException, 
		FileNotFoundException,
		IOException
	{
		// Turn off certificate checks if necessary
		if ( hasOption( "ignoresslerrors" ) )
		{
			System.setProperty(
				"org.apache.axis.components.net.SecureSocketFactory",
				"org.apache.axis.components.net.SunFakeTrustSocketFactory" );
		}

		// Even though the ServiceInstance is a special ManagedObject,
		// it is still a ManagedObject and you pass it to the Web
		// Service with a ManagedObjectReference (MoRef), in this case,
		// vim_svc_ref
		ManagedObjectReference vim_svc_ref = null;

		// Get the MoRef for the ServiceInstance
		vim_svc_ref = new ManagedObjectReference();
		vim_svc_ref.setType( "ServiceInstance" );
		vim_svc_ref.set_value( "ServiceInstance" );

		VimServiceLocator vim_svc_loc = new VimServiceLocator();
		vim_svc_loc.setMaintainSession( true );

		m_vim_svc = vim_svc_loc.getVimPort( new java.net.URL( getVIUrl() ) );
		VimBindingStub vbs = ( VimBindingStub ) m_vim_svc;
		vbs.setMaintainSession( true );
		
		if ( getOptionValue( "sessionfile" ) != null )
		{
			/*
			 * Load the session cookie. It is important that when using Axis,
			 * the session cookie is loaded BEFORE the service content is 
			 * retrieved, otherwise the session will not be authenticated.
			 */
			String session_file = getOptionValue( "sessionfile" );
			BufferedReader br = 
				new BufferedReader( new FileReader( session_file ) );
			String session_cookie = br.readLine();
			br.close();
			vbs._setProperty( 
				org.apache.axis.transport.http.HTTPConstants.HEADER_COOKIE,
				session_cookie );
			m_vim_svc_content = m_vim_svc.retrieveServiceContent( vim_svc_ref );
		}
		else
		{
			m_vim_svc_content = m_vim_svc.retrieveServiceContent( vim_svc_ref );
			m_vim_svc.login( m_vim_svc_content.getSessionManager(),
				getOptionValue( "username" ), getOptionValue( "password" ),
				null );
		}
		
		m_is_connected = true;
	}

	/**
	 * Disconnects from the VI server.
	 * 
	 * @throws RemoteException If there is an error disconnected from the
	 *         server.
	 */
	public void disconnect() throws RemoteException
	{
		if ( !m_is_connected ) return;
		m_vim_svc.logout( m_vim_svc_content.getSessionManager() );
		m_is_connected = false;
	}

	/**
	 * Save the current session cookie in Axis format.
	 * 
	 * @throws java.io.IOException If error writing session file.
	 */
	public void saveSession() throws java.io.IOException
	{
		saveSession( getOptionValue( "savesessionfile" ) );
	}

	/**
	 * Save the current session cookie to the given file in Axis format.
	 * 
	 * @param sessionFile The file to save the session cookie to.
	 * @throws java.io.IOException If error writing session file.
	 */
	public void saveSession( String sessionFile ) throws java.io.IOException
	{
		saveSession( getOptionValue( "savesessionfile" ),
			SaveSessionCookieFormat.AXIS );
	}

	/**
	 * Save the current session cookie to the given file in the given format.
	 * 
	 * @param sessionFile The file to save the session cookie to.
	 * @param format The format to save the session cookie as.
	 * @throws java.io.IOException If error writing session file.
	 */
	public void saveSession( 
		String sessionFile, 
		SaveSessionCookieFormat format )
		throws java.io.IOException
	{
		java.io.FileWriter fout = new java.io.FileWriter( sessionFile );

		Object ocookie = ( ( VimBindingStub ) m_vim_svc )._getCall()
			.getMessageContext().getProperty( 
			org.apache.axis.transport.http.HTTPConstants.HEADER_COOKIE );
		String socookie = String.valueOf( ocookie );
		
		// vmware_soap_session=\"F25551E7-D721-4992-8A24-B82A0E98A6AC\"
		String scookie = String.valueOf( socookie ).replaceAll( "\"", "" );
		scookie = scookie.replaceAll( "vmware_soap_session=", "" );

		switch (format)
		{
			case AXIS :
			{
				fout.write( socookie );
				break;
			}
			case COOKIE_VALUE_ONLY :
			{
				fout.write( scookie );
				break;
			}
			case PERL :
			{
				String s = String.format(
					"#LWP-Cookies-1.0\nSet-Cookie3: vmware_soap_session="
					+ "\"\\\"%1$b\\\"\"; path=\"/\"; domain={%2$b}; path_spec; "
					+ "discard; version=0", scookie,
					getOptionValue( "server" ) );
				fout.write( s );
				break;
			}
		}

		fout.close();
	}
	
	/**
	 * @param <T> The type of property to return.
	 * @param moref The managed object reference to get the properties from.
	 * @param propertyName The name of the property to return.
	 * @return If the property name exists its value is returned; otherwise 
	 * null.
	 * @throws InvalidProperty If the given property name is invalid.
	 * @throws RemoteException If there is a problem communicating with the
	 * server.
	 */
	public <T> T getProperty(
		final ManagedObjectReference moref, final String propertyName )
		throws InvalidProperty, RemoteException
	{
		HashMap<String, T> values = getProperties( moref,
			new String[] { propertyName } );
		
		if ( !values.containsKey( propertyName ) ) return null;
		else return values.get( propertyName );
	}

	/**
	 * Retrieves several or all property values from a managed object. 
	 * If the specified Type is not Object, then all of the property
	 * values must be of the same type (type T).
	 * 
	 * @param <T> The type of the properties to return.
	 * @param moref The managed object reference to get the properties from.
	 * @param propertyNames The name of the properties to return.
	 * @return A HashMap keyed on property names. An empty HashMap is returned
	 *         if no property values were found.
	 * @throws RemoteException If there is an error communicating with the VI
	 *         server.
	 * @throws InvalidProperty If an invalid property name is submitted.
	 */
	@SuppressWarnings( "unchecked" )
	public <T> HashMap<String, T> getProperties(
		final ManagedObjectReference moref, final String[] propertyNames )
		throws InvalidProperty, RemoteException
	{
		PropertySpec pspec = new PropertySpec();
		pspec.setType( moref.getType() );
		
		if ( propertyNames == null ) pspec.setAll(  true );
		else pspec.setPathSet( propertyNames );

		ObjectSpec ospec = new ObjectSpec();
		ospec.setObj( moref );

		PropertyFilterSpec pfspec = new PropertyFilterSpec();
		pfspec.setObjectSet( new ObjectSpec[]
			{
				ospec
			} );
		pfspec.setPropSet( new PropertySpec[]
			{
				pspec
			} );

		ObjectContent[] oc =
			m_vim_svc.retrieveProperties( m_vim_svc_content
				.getPropertyCollector(), new PropertyFilterSpec[]
				{
					pfspec
				} );

		HashMap<String, T> values = new HashMap<String, T>();

		if ( oc[ 0 ].getPropSet() != null )
		{
			for ( int x = 0; x < oc[ 0 ].getPropSet().length; ++x )
			{
				values.put( oc[ 0 ].getPropSet()[ x ].getName(), ( T ) oc[ 0 ]
					.getPropSet()[ x ].getVal() );
			}
		}

		return (values);
	}
	
	/**
	 * Retrieves all the available properties of a given managed object 
	 * reference.
	 * @param moref The managed object reference to get the properties for.
	 * @return A HashMap keyed on property names. An empty HashMap is returned
	 *         if no property values were found.
	  * @throws RemoteException If there is an error communicating with the VI
	 *         server.
	 * @throws InvalidProperty If an invalid property name is submitted.
	 */
	public HashMap<String, Object> getProperties(
		final ManagedObjectReference moref ) 
		throws InvalidProperty, RemoteException
	{
		return ( getProperties( moref, null ) );
	}
	
	/**
	 * Finds one of the entities on the VI servers that is one of the following
	 * entity types and matches the filter criteria:
	 * 
	 * <ul>
	 * 	<li>ClusterComputeResource</li>
	 * 	<li>ComputeResource</li>
	 * 	<li>Datacenter</li>
	 * 	<li>Folder</li>
	 * 	<li>HostSystem</li>
	 * 	<li>ResourcePool</li>
	 * 	<li>VirtualMachine</li>
	 * </ul>
	 * 
	 * @param entityType The type of entity to search for.
	 * @param filters The filters used to restrict the search. This property is
	 * valid for simple types (String, int, boolean) only. Regular expressions
	 * are allowed, but they are case sensitive. To match a boolean value, use
	 * "True" or "False". A filter is a HashMap item whose key is a valid
	 * property path and value is the property path's desired value. A filter
	 * that has a property path which is invalid for the type of entity being
	 * search for will cause this method to throw and exception. Any entity 
	 * found with this method must match all of the filters specified to be
	 * returned.
	 * @param rootOfSearch A managed object reference to the entity on the VI
	 * server where the search should begin. For example, instead of starting 
	 * the search at the root folder, you can provide a reference to a resource
	 * pool in order to narrow the search for a given VM.
	 * @return A  managed object reference to the entity that was
	 * discovered and met the filter criteria. If none are found then
	 * an empty list is returned. If more than entity was found that 
	 * met the filter criteria only the first result is returned. If
	 * no entity was found a null value is returned.
	 * @throws RemoteException If there is a problem communicating with the VI
	 * server.
	 * @throws InvalidProperty If an invalid property name is submitted.
	 * @throws Exception If the root of the search is not a valid entity. 
	 */
	public ManagedObjectReference findEntity(
		EntityTypes entityType,
		HashMap<String, String> filters,
		ManagedObjectReference rootOfSearch )
		throws InvalidProperty, RemoteException, Exception
	{
		List<ManagedObjectReference> list =
			findEntities( entityType, filters, rootOfSearch );
		if ( list.size() == 0 ) return null;
		else return list.get( 0 );
	}
	
	/**
	 * Finds one of the entities on the VI servers that is one of the following
	 * entity types and matches the filter criteria:
	 * 
	 * <ul>
	 * 	<li>ClusterComputeResource</li>
	 * 	<li>ComputeResource</li>
	 * 	<li>Datacenter</li>
	 * 	<li>Folder</li>
	 * 	<li>HostSystem</li>
	 * 	<li>ResourcePool</li>
	 * 	<li>VirtualMachine</li>
	 * </ul>
	 * 
	 * @param entityType The type of entity to search for.
	 * @param filters The filters used to restrict the search. This property is
	 * valid for simple types (String, int, boolean) only. Regular expressions
	 * are allowed, but they are case sensitive. To match a boolean value, use
	 * "True" or "False". A filter is a HashMap item whose key is a valid
	 * property path and value is the property path's desired value. A filter
	 * that has a property path which is invalid for the type of entity being
	 * search for will cause this method to throw and exception. Any entity 
	 * found with this method must match all of the filters specified to be
	 * returned.
	 * @return A  managed object reference to the entity that was
	 * discovered and met the filter criteria. If none are found then
	 * an empty list is returned. If more than entity was found that 
	 * met the filter criteria only the first result is returned. If
	 * no entity was found a null value is returned.
	 * @throws RemoteException If there is a problem communicating with the VI
	 * server.
	 * @throws InvalidProperty If an invalid property name is submitted.
	 * @throws Exception If the root of the search is not a valid entity. 
	 */
	public ManagedObjectReference findEntity(
		EntityTypes entityType,
		HashMap<String, String> filters )
		throws InvalidProperty, RemoteException, Exception
	{
		return ( findEntity( entityType, filters, null ) );
	}
	
	/**
	 * Finds all of the entities on the VI server that are one of the following
	 * entity types:
	 * <ul>
	 * 	<li>ClusterComputeResource</li>
	 * 	<li>ComputeResource</li>
	 * 	<li>Datacenter</li>
	 * 	<li>Folder</li>
	 * 	<li>HostSystem</li>
	 * 	<li>ResourcePool</li>
	 * 	<li>VirtualMachine</li>
	 * </ul>
	 * @param entityType The type of entity to search for.
	 * @param rootOfSearch A managed object reference to the entity on the VI
	 * server where the search should begin. For example, instead of starting 
	 * the search at the root folder, you can provide a reference to a resource
	 * pool in order to narrow the search for a given VM.
	 * @return A list of managed object references to the entities that were
	 * discovered. If none are found then an empty
	 * list is returned.
	 * @throws RemoteException If there is a problem communicating with the VI
	 * server.
	 * @throws InvalidProperty If an invalid property name is submitted.
	 * @throws Exception If the root of the search is not a valid entity. 
	 */
	public List<ManagedObjectReference> findEntities(
		final EntityTypes entityType,
		final ManagedObjectReference rootOfSearch ) 
		throws InvalidProperty, RemoteException, Exception
	{
		return ( findEntities( entityType, null, rootOfSearch ) );
	}
	
	/**
	 * Finds all of the entities on the VI server that are one of the following
	 * entity types and match the given filter criteria:
	 * <ul>
	 * 	<li>ClusterComputeResource</li>
	 * 	<li>ComputeResource</li>
	 * 	<li>Datacenter</li>
	 * 	<li>Folder</li>
	 * 	<li>HostSystem</li>
	 * 	<li>ResourcePool</li>
	 * 	<li>VirtualMachine</li>
	 * </ul>
	 * @param entityType The type of entity to search for.
	 * @param filters The filters used to restrict the search. This property is
	 * valid for simple types (String, int, boolean) only. Regular expressions
	 * are allowed, but they are case sensitive. To match a boolean value, use
	 * "True" or "False". A filter is a HashMap item whose key is a valid
	 * property path and value is the property path's desired value. A filter
	 * that has a property path which is invalid for the type of entity being
	 * search for will cause this method to throw and exception. Any entity 
	 * found with this method must match all of the filters specified to be
	 * returned.
	 * @return A list of managed object references to the entities that were
	 * discovered and met the filter criteria. If none are found then an empty
	 * list is returned.
	 * @throws RemoteException If there is a problem communicating with the VI
	 * server.
	 * @throws InvalidProperty If an invalid property name is submitted.
	 * @throws Exception If the root of the search is not a valid entity. 
	 */
	public List<ManagedObjectReference> findEntities(
		EntityTypes entityType,
		HashMap<String, String> filters )
		throws InvalidProperty, RemoteException, Exception
	{
		return ( findEntities( entityType, filters, null ) );
	}
	
	/**
	 * Finds all of the entities on the VI server that are one of the following
	 * entity types:
	 * <ul>
	 * 	<li>ClusterComputeResource</li>
	 * 	<li>ComputeResource</li>
	 * 	<li>Datacenter</li>
	 * 	<li>Folder</li>
	 * 	<li>HostSystem</li>
	 * 	<li>ResourcePool</li>
	 * 	<li>VirtualMachine</li>
	 * </ul>
	 * @param entityType The type of entity to search for.
	 * @return A list of managed object references to the entities that were
	 * discovered. If none are found then an empty list is returned.
	 * @throws RemoteException If there is a problem communicating with the VI
	 * server.
	 * @throws InvalidProperty If an invalid property name is submitted.
	 * @throws Exception If the root of the search is not a valid entity. 
	 */
	public List<ManagedObjectReference> findEntities(
		EntityTypes entityType )
		throws InvalidProperty, RemoteException, Exception
	{
		return ( findEntities( entityType, null, null ) );
	}
	
	/**
	 * Finds all of the entities on the VI server that are one of the following
	 * entity types and match the given filter criteria:
	 * <ul>
	 * 	<li>ClusterComputeResource</li>
	 * 	<li>ComputeResource</li>
	 * 	<li>Datacenter</li>
	 * 	<li>Folder</li>
	 * 	<li>HostSystem</li>
	 * 	<li>ResourcePool</li>
	 * 	<li>VirtualMachine</li>
	 * </ul>
	 * @param entityType The type of entity to search for.
	 * @param filters The filters used to restrict the search. This property is
	 * valid for simple types (String, int, boolean) only. Regular expressions
	 * are allowed, but they are case sensitive. To match a boolean value, use
	 * "True" or "False". A filter is a HashMap item whose key is a valid
	 * property path and value is the property path's desired value. A filter
	 * that has a property path which is invalid for the type of entity being
	 * search for will cause this method to throw and exception. Any entity 
	 * found with this method must match all of the filters specified to be
	 * returned.
	 * @param rootOfSearch A managed object reference to the entity on the VI
	 * server where the search should begin. For example, instead of starting 
	 * the search at the root folder, you can provide a reference to a resource
	 * pool in order to narrow the search for a given VM.
	 * @return A list of managed object references to the entities that were
	 * discovered and met the filter criteria. If none are found then an empty
	 * list is returned.
	 * @throws RemoteException If there is a problem communicating with the VI
	 * server.
	 * @throws InvalidProperty If an invalid property name is submitted.
	 * @throws Exception If the root of the search is not a valid entity. 
	 */
	public List<ManagedObjectReference> findEntities(
		final EntityTypes entityType,
		HashMap<String,String> filters,
		ManagedObjectReference rootOfSearch ) 
		throws InvalidProperty, RemoteException, Exception
	{
		if ( filters == null ) filters = new HashMap<String, String>();

		if ( rootOfSearch == null ) 
			rootOfSearch = m_vim_svc_content.getRootFolder();

		EntityTypes root_type = null;
		try
		{
			root_type = Enum.valueOf(  
				EntityTypes.class, rootOfSearch.getType() );
		}
		catch ( Exception e ) { }
		if ( root_type == null )
		{
			throw new Exception( "rootOfSearch is not a valid entity" );
		}

		PropertySpec pspec = new PropertySpec();
		pspec.setAll( false );
		pspec.setType( entityType.toString() );
		String[] paths = new String[ filters.keySet().size() ];
		paths = filters.keySet().toArray( paths );
		pspec.setPathSet( paths );

		PropertyFilterSpec pfspec = getSearchFilterSpec(
			rootOfSearch, pspec );

		ObjectContent[] ocs = m_vim_svc.retrieveProperties(
			m_vim_svc_content.getPropertyCollector(),
			new PropertyFilterSpec[] { pfspec } );
		
		boolean ismatch = false;
		List<ManagedObjectReference> matches = 
			new ArrayList<ManagedObjectReference>();

		for ( ObjectContent oc : ocs )
		{
			ismatch = filters.size() == 0;
			if ( !ismatch )
			{
				for ( DynamicProperty dp : oc.getPropSet() )
				{
					String sval = String.valueOf( dp.getVal() );
					ismatch = sval.matches( filters.get( dp.getName() ) );
					if ( !ismatch ) break;
				}
			}
			if ( ismatch ) matches.add( oc.getObj() );
		}

		return matches;
	}
	
	/**
	 * Get a property filter specification used to search the VI server.
	 * @param moref The managed object reference at which to begin the search.
	 * @param propertySpec The property filter specification used in the search.
	 * @return A property filter specification that can be used to traverse all
	 * of the entities in VI.
	 */
	public PropertyFilterSpec getSearchFilterSpec(
		final ManagedObjectReference moref,
		final PropertySpec propertySpec )
	{
		TraversalSpec rp2rp = new TraversalSpec();
		rp2rp.setName( "resourcePoolTraversalSpec" );
		rp2rp.setType( "ResourcePool" );
		rp2rp.setPath( "resourcePool" );
		rp2rp.setSkip( false );
		rp2rp.setSelectSet( new SelectionSpec[]
		{
			getSelectionSpec( "resourcePoolTraversalSpec" ),
			getSelectionSpec( "resourcePoolVmTraversalSpec" )
		} );

		TraversalSpec rp2vm = new TraversalSpec();
		rp2vm.setName( "resourcePoolVmTraversalSpec" );
		rp2vm.setType( "ResourcePool" );
		rp2vm.setPath( "vm" );
		rp2vm.setSkip( false );

		TraversalSpec cr2rp = new TraversalSpec();
		cr2rp.setName( "computeResourceRpTraversalSpec" );
		cr2rp.setType( "ComputeResource" );
		cr2rp.setPath( "resourcePool" );
		cr2rp.setSkip( false );
		cr2rp.setSelectSet( new SelectionSpec[]
		{
			getSelectionSpec( "resourcePoolTraversalSpec" ),
			getSelectionSpec( "resourcePoolVmTraversalSpec" )
		} );

		TraversalSpec cr2h = new TraversalSpec();
		cr2h.setName( "computeResourceHostTraversalSpec" );
		cr2h.setType( "ComputeResource" );
		cr2h.setPath( "host" );
		cr2h.setSkip( false );

		TraversalSpec dc2h = new TraversalSpec();
		dc2h.setName( "datacenterHostTraversalSpec" );
		dc2h.setType( "Datacenter" );
		dc2h.setPath( "hostFolder" );
		dc2h.setSkip( false );
		dc2h.setSelectSet( new SelectionSpec[]
		{
			getSelectionSpec( "folderTraversalSpec" ),
		} );

		TraversalSpec dc2vm = new TraversalSpec();
		dc2vm.setName( "datacenterVmTraversalSpec" );
		dc2vm.setType( "Datacenter" );
		dc2vm.setPath( "vmFolder" );
		dc2vm.setSkip( false );
		dc2vm.setSelectSet( new SelectionSpec[]
		{
			getSelectionSpec( "folderTraversalSpec" ),
		} );

		TraversalSpec h2vm = new TraversalSpec();
		h2vm.setName( "hostVmTraversalSpec" );
		h2vm.setType( "HostSystem" );
		h2vm.setPath( "vm" );
		h2vm.setSkip( false );
		h2vm.setSelectSet( new SelectionSpec[]
		{
			getSelectionSpec( "folderTraversalSpec" ),
		} );

		TraversalSpec f = new TraversalSpec();
		f.setName( "folderTraversalSpec" );
		f.setType( "Folder" );
		f.setPath( "childEntity" );
		f.setSkip( false );
		
		f.setSelectSet( new SelectionSpec[]
		{
			getSelectionSpec( "folderTraversalSpec" ),
			getSelectionSpec( "datacenterHostTraversalSpec" ),
			getSelectionSpec( "datacenterVmTraversalSpec" ),
			getSelectionSpec( "computeResourceRpTraversalSpec" ),
			getSelectionSpec( "computeResourceHostTraversalSpec" ),
			getSelectionSpec( "hostVmTraversalSpec" ),
			getSelectionSpec( "resourcePoolVmTraversalSpec" )
		} );

		ObjectSpec ospec = new ObjectSpec();
		ospec.setObj( moref );
		ospec.setSkip( false );
		ospec.setSelectSet( new SelectionSpec[]
		{
			f,
			dc2vm,
			dc2h,
			cr2h,
			cr2rp,
			rp2rp,
			h2vm,
			rp2vm
		} );

		PropertyFilterSpec pfspec = new PropertyFilterSpec();
		pfspec.setPropSet( new PropertySpec[] { propertySpec } );
		pfspec.setObjectSet( new ObjectSpec[] { ospec } );

		return ( pfspec );
	}
	
	/**
	 * Returns a new selection specification with the name set to the given
	 * name.
	 * @param name The name of the selection specification to return.
	 * @return The new selection specification.
	 */
	private SelectionSpec getSelectionSpec( String name )
	{
		SelectionSpec ss = new SelectionSpec();
		ss.setName( name );
		return ( ss );
	}

	// </editor-fold>

	// </region>
}
